<template>
  <div class="uploadnew" :class="{ [upClass]: true }" ref="uploadNewRef">
    <!-- 隐藏的原生文件选择输入框 -->

    <input
      v-if="fileInputShow"
      type="file"
      ref="fileInputRef"
      style="display: none"
      :multiple="isMultiple"
      @change="handleFileChange"
      :accept="acceptType"
    />
    <div class="uploadbtn" @click="openFileInput">
      <slot>
        <el-button type="primary">上传</el-button>
      </slot>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { ref, watch, reactive } from 'vue'
import SparkMD5 from 'spark-md5'
import BreakPointAPI from '@/api/breakpoint'
import { ElMessage } from 'element-plus'
import { createPollingManager } from '@/utils/pollingManager'
import { useGlobalUploadDialogStore } from '@/store'

const { showDialog, setUploadInfo } = useGlobalUploadDialogStore()
const pollingManager = createPollingManager()

const fileInputRef = ref<HTMLInputElement | null>(null)
const uploadNewRef = ref<HTMLDivElement | null>(null)

const props = withDefaults(
  defineProps<{
    /**
     * 单文件最大上传大小
     */
    isMultiple?: boolean
    /**
     * 上传组件的class
     */
    upClass?: string
    /**
     * 上传文件存入的文件夹id
     */
    folderId?: number
    /**
     * isAudioPro 判断是否是音视频文件
     */
    isAudioPro?: boolean
    /**
     * 允许上传的文件类型（上传时浏览文件夹可以选用的类型）
     */
    acceptType?: string
    /**
     * 单文件最大上传大小
     */
    fileMaxSize?: number

    isStore?: boolean
  }>(),
  {
    isMultiple: true,
    upClass: '',
    folderId: 0,
    isAudioPro: false,
    isStore: false,
    acceptType: () => {
      // 常用图片格式后缀
      const imageFormats = [
        'png',
        'bmp',
        'jpg',
        'jpe',
        'svg',
        'jpeg',
        'tiff',
        'tif',
        'ico',
        'webp',
        'jxr',
        'hdp',
        'raw',
        'psd',
        'ai',
      ]

      // 常用视频格式后缀
      const videoFormats = [
        'mp4',
        '3gp',
        'avi',
        'wmv',
        'webm',
        'mkv',
        'vob',
        'ojv',
        'rmvb',
        'mpeg',
        'mpg',
        'mov',
        'flv',
        'swf',
        'qsv',
        'kux',
      ]

      // 常用音频格式后缀
      const audioFormats = [
        'mp3',
        'ac3',
        'amr',
        'ogg',
        'mp2',
        'm4a',
        'aac',
        'flac',
        'mp1',
        'wav',
        'wma',
        'aif',
        'aiff',
        'au',
        'ram',
        'mid',
        'rmi',
      ]

      // 将所有后缀格式合并成一个字符串，并用逗号分隔
      const allFormats = [
        ...imageFormats,
        ...videoFormats,
        ...audioFormats,
      ].map((ext) => '.' + ext)
      const formattedString = allFormats.join(',')
      return formattedString
    },
    fileMaxSize: 300 * 1024 * 1024,
  }
)

// const allFormatsString = '.jpg, .jpeg, .png, .gif, .bmp, .webp, .svg, .mp4, .avi, .mkv, .mov, .wmv, .flv, .webm, .mp3, .wav, .ogg, .aac, .flac, .wma, .m4a'

// console.log(allFormatsString)

const emit = defineEmits<{
  (e: 'breakpointContinueFinishE', params: any): void
}>()

const fileInputShow = ref(true)

// 从新渲染上传dom元素
const createNewInput = () => {
  fileInputShow.value = false
  fileInputShow.value = true
}

// 触发隐藏的文件选择输入框
const openFileInput = () => {
  // 调用createNewInput函数来创建新的输入元素
  createNewInput()

  // 模拟点击input:file 上传组件
  if (fileInputRef.value) {
    fileInputRef.value.click()
  }
}

// 文件信息状态数据
interface FileUpload {
  // 文件名称
  name: string
  // 文件大小
  size: number
  // 总文件md5
  fileMd5: string

  // 是否正在上传
  uploading: boolean
  // 上传进度
  uploadProgress: number
  // 上传是否完成
  uploaded: boolean
  // 是否正在转码
  transcoding: boolean
  // 转码进度
  transcodeProgress: number
  // 转码是否完成
  transcoded: boolean
}

// 最大上传文件大小
const maxSize = ref(props.fileMaxSize)

// 允许上传的文件类型
const allowFileTypes = ref([
  'image/png',
  'image/jpg',
  'image/jpeg',
  'image/bmp',
  'image/tif',
  'image/tiff',
  'audio/wav',
  'audio/mp3',
  'audio/aac',
  'audio/flac',
  'audio/ogg',
  'audio/wma',
  'audio/mpeg',
  'audio/aiff',
  'audio/aif',
  'audio/ac3',
  'audio/amr',
  'audio/m4a',
  'video/mp4',
  'video/m4v',
  'video/quicktime',
  'video/mov',
  'video/3gp',
  'video/3g2',
  'video/mkv',
  'video/webm',
  'video/avi',
  'video/wmv',
  'video/flv',
  'video/f4v',
  'video/m3u8',
  'video/mpeg',
  'video/ogv',
  'video/rmvb',
])

// 不需要转码的文件类型（扩展名）目前要求所有文件都走转码
const notAllowTranscodingFileTypes = ref([])

// 待上传文件数组
const prepareFiles = ref([])

// 待上传文件上传进度信息数组
const prepareUploadInfo = ref({})

type AllSlicesType = {
  [key: string]: Array<FormData>
}
// 所有文件待上传文件总切片数组
const allSlices = ref<AllSlicesType>({})

// 所有待上传文件 需要上传的切片 数组（断点续传情况）
const waitSlices = ref([])

// 未进入待上传文件的错误信息（不符合文件大小和类型的文件）
const perErrMsg = ref([])

const rand = ref(Math.random())

// 上传的文件夹
const folderIDd = ref(props.folderId)

watch(
  () => props.folderId,
  (val) => {
    folderIDd.value = val
  }
)

// 监听待上传文件上传进度信息数组
watch(
  prepareUploadInfo,
  () => {
    setUploadInfo(prepareUploadInfo.value)
  },
  {
    deep: true,
  }
)

/**
 * 选择文件，上传文件数据初始化（待上传数组，待上传文件信息和上传进度信息，切片数据初始化）
 * @param event
 */
const handleFileChange = async (event: Event) => {
  const input = event.target as HTMLInputElement
  const fileList = input.files

  console.log(fileList.length, 'fileList.lengthfileList.lengthfileList.length')

  if (fileList) {
    for (let i = 0; i < fileList.length; i++) {
      const file = fileList[i]
      // 验证文件的大小和类型
      if (checkFile(file)) {
        // 获取文件md5唯一值
        const fileMd5: string = (await getFileMd5(file)) + rand.value

        console.log(fileMd5, 'fileMd5')

        // 待上传文件数组
        const preItem = {
          fileValue: file,
          fileMd5: fileMd5,
        }
        prepareFiles.value.push(preItem)
        console.log(
          file.name,
          'file.namefile.namefile.namefile.namefile.namefile.namefile.namefile.namefile.namefile.namefile.namefile.namefile.namefile.namefile.namefile.namefile.namefile.namefile.name'
        )

        // 待上传文件上传进度信息数组
        const preInfo = {
          name: formatStrA(file.name),
          size: file.size,
          fileType: getFileTypeByExtension(file.name), // 获取文件类型（1:图片、2:视频、3:音频、4:未知
          fileMd5: fileMd5,
          uploaded: false,
          uploading: false,
          uploadProgress: 0,
          transcoding: false,
          transcodeProgress: 0,
          transcoded: false,
        }
        prepareUploadInfo.value[fileMd5] = preInfo
        // 切片数据初始化(创建切片数组：将文件分割为切片数组，每个单元有（fileMd5标识，切片后文件，切片计数，切片文件名）)
        createFileSlice(file, fileMd5)
      }
    }
    showDialog()
    // 查找所有文件
    console.log(prepareFiles.value, 'prepareFilesprepareFiles')
    console.log(prepareUploadInfo.value, 'prepareUploadInfoprepareUploadInfo')
    console.log(allSlices.value, 'allSlicesallSlices')
    // 查询文件是否上传(对上传文件做后端校验（判断文件是否上传，对未上传或未完整上传文件的切片做点断处理）)
    await findFiles()
    console.log(prepareUploadInfo.value, 'prepareUploadInfo.value')
    console.log(waitSlices.value, 'waitSlices.valuewaitSlices.value')

    // 重组待上传数据,上传切片！！！
    await recombineWaitSlices()
    // console.log(waitSlices.value, 'waitSlices.valuewaitSlices.value')
  }
}

/**
 * 同步切片上传方法
 * @param item
 */
const asyncUpLoadFileSlice = (item: { formData: any }) => {
  // 切片上传
  return new Promise((resolve, reject) => {
    BreakPointAPI.breakpointContinue(item.formData)
      .then((fileRe: { code: number }) => {
        if (fileRe.code !== 0) {
          resolve(false)
        } else {
          console.log('上传切片', new Date().getTime())
          resolve(true)
        }
      })
      .catch((error: any) => {
        reject(error)
      })
  })
}

/**
 * 切片上传异步方法
 * @param _objP
 */
const upLoadFileSlice = async (_objP: {
  item: any
  fileMd5: any
  index: any
}) => {
  const { item, fileMd5, index } = _objP

  const res = await BreakPointAPI.breakpointContinue(item.formData)
  if (res.code === 0) {
    // 切片上传成功
    // 1.更新切片是否上传完成的 isFinish 数据
    waitSlices.value[fileMd5][index].isFinish = true // 设置当前切片上传完成
    // 2.###切片上传成功，更新切片上传进度
    updateUploadProgress(fileMd5)
    // 判断当前的文件所有切片是否完成
    const isAllFinish = checkAllSliceFinish(fileMd5)
    // 所有切片上传完成，清理切片数据，合成切片，转码
    if (isAllFinish) {
      // 清理切片数据
      removeWaitSlices(fileMd5)
      removeAllSlices(fileMd5)
      // 合成切片，转码
      compositeFile(fileMd5)
    }
  }
}

const removeWaitSlices = (fileMd5: string | number) => {
  // 使用 Vue 提供的 API（reactive 或 ref）来确保响应式系统能够追踪到对象属性的变化
  // 这里我们使用 reactive 方法来创建一个非 ref 的响应式对象
  const updatedItems = reactive({ ...waitSlices.value })
  // 删除第元素
  delete updatedItems[fileMd5]
  // 更新 ref 的 value，使其成为响应式对象
  waitSlices.value = updatedItems
}

const removeAllSlices = (fileMd5: string | number) => {
  // 使用 Vue 提供的 API（reactive 或 ref）来确保响应式系统能够追踪到对象属性的变化
  // 这里我们使用 reactive 方法来创建一个非 ref 的响应式对象
  const updatedItems = reactive({ ...allSlices.value })
  // 删除第元素
  delete updatedItems[fileMd5]
  // 更新 ref 的 value，使其成为响应式对象
  allSlices.value = updatedItems
}

/**
 * 更新切片上传进度
 * @param fileMd5
 */
const updateUploadProgress = (fileMd5: string | number) => {
  let finishedNum = 0
  // 获取已上传切片个数
  for (const item of waitSlices.value[fileMd5]) {
    if (item.isFinish) {
      finishedNum++
    }
  }
  // 获取切片百分比整数
  const percentum = Math.ceil(
    parseFloat((finishedNum / waitSlices.value[fileMd5].length).toFixed(2)) *
      100
  )
  prepareUploadInfo.value[fileMd5].uploadProgress = percentum
  console.log(percentum, 'percentumpercentum')
}

/**
 * 重组 所有待上传文件 需要上传的切片 数组 waitSlices
 */
const recombineWaitSlices = async () => {
  const waitsliceskey = Object.keys(waitSlices.value)
  for (const wk of waitsliceskey) {
    console.log(waitSlices.value[wk], 'waitSlices.value[wk]')

    for (const [index, item] of waitSlices.value[wk].entries()) {
      // 重组
      // waitSlices.value[wk][index].formData.append('chunkTotal', allSlices.value[wk].length) // 切片总数携带给后台 总有用的
      item.formData.append('chunkTotal', allSlices.value[wk].length) // 切片总数携带给后台 总有用的
      const fileF = item.formData.get('file')
      const fileMd5: string = await getFileMd5(fileF) // 获取当前切片md5 后端用于验证切片完整性
      // waitSlices.value[wk][index].formData.append('chunkMd5', fileMd5)
      item.formData.append('chunkMd5', fileMd5)
      // 上传切片
      upLoadFileSlice({ item, fileMd5: wk, index })
    }
  }
}

/**
 * 所有切片上传完成，通知后端合成切片，如果文件需要转码，并开启轮询判断需要转码的文件是否转码完成
 * @param fileMd5 文件的md5唯一标识
 */
const compositeFile = async (fileMd5: string | number) => {
  // 切片传完以后 合成文件
  const params = {
    fileName: prepareUploadInfo.value[fileMd5].name, // 文件名，用于存储
    fileMd5: fileMd5, // 文件的 fileMd5 后端用来识别合成那些对应的切片
    fileSource: 'my', // 文件来源，也是文件用途标识
    folderId: folderIDd.value, // 文件夹id 对应文件存储的文件夹
  }
  console.log('合成文件', new Date().getTime())
  let res = null
  if (props.isAudioPro) {
    res = await BreakPointAPI.breakpointContinueFinishAudio(params)
  } else {
    res = await BreakPointAPI.breakpointContinueFinish(params)
  }
  // let res = await breakpointContinueFinish(params)
  if (res.code === 0) {
    // ###更新上传进度数据
    prepareUploadInfo.value[fileMd5].uploaded = true

    // TODO:合成文件过后 删除缓存切片
    // ElMessage.success('上传成功')
    const ext = getFileExtension(params.fileName)
    // 判断文件后缀是否需要转码
    if (!notAllowTranscodingFileTypes.value.includes(ext)) {
      // 查看转码进度
      // 创建轮询对象开始轮询处理（判断是否转发成功）
      pollingManager.addPollingInstance(pollingIntervalPreview, 3000, {
        fileMd5,
      })
    } else {
      // 不转码直接完成
      console.log('无需转码：', params.fileName)
      // ###更新上传进度数据
      prepareUploadInfo.value[fileMd5].transcoding = true
      prepareUploadInfo.value[fileMd5].transcodeProgress = 100
      prepareUploadInfo.value[fileMd5].transcoded = true
      emit('breakpointContinueFinishE', {
        ...res.data,
        folderIDd: folderIDd.value,
        emitType: 1,
        emitMsg: '无需转码',
      })
    }
  }
}

// 寻轮最大次数控制步值
const cc = ref(0)
/**
 * 预览生成轮询方法
 * @param stop
 */
const pollingIntervalPreview = async (
  stop: () => void,
  param: { fileMd5: any }
) => {
  const { fileMd5 } = param
  // ###更新上传进度数据
  prepareUploadInfo.value[fileMd5].transcoding = true

  cc.value++
  // 请求后端，判断是否转码完成
  const res = await BreakPointAPI.finishGetTranscodeRecord({
    fileMd5,
  })
  // 控制最大轮询次数
  if (cc.value === 99999999999) {
    stop()
  }
  if (res.code === 0) {
    if (res.data.transcode_type === 1) {
      // 志超代码
      // if (props.isStore) {
      //   shearStore.setFile(res.data)
      // }

      // 转码成功
      // ###更新上传进度数据
      prepareUploadInfo.value[fileMd5].transcodeProgress = 100
      prepareUploadInfo.value[fileMd5].transcoded = true

      console.log(' emit(breakpointContinueFinishE')

      emit('breakpointContinueFinishE', {
        ...res.data,
        folderIDd: folderIDd.value,
        emitType: 2,
        emitMsg: '转码成功',
      })

      stop()
    }
    console.log(res.data, 'finishGetTranscodeRecord')
  } else {
    ElMessage({
      message: '转码失败',
      type: 'warning',
      showClose: true,
      duration: 0,
    })

    stop()
  }
}

// 验证所有切片是否完成
const checkAllSliceFinish = (fileMd5: string | number) => {
  for (const item of waitSlices.value[fileMd5]) {
    if (!item.isFinish) {
      console.log('切片没有完成上传')
      return false
    }
  }
  console.log('所有切片已完成', waitSlices.value[fileMd5])

  return true
}

/**
 * 对上传文件做后端校验（判断文件是否上传，对未上传或未完整上传文件的切片做点断处理）
 */
const findFiles = async () => {
  if (!(prepareFiles.value.length > 0)) {
    return
  }

  for (const item of prepareFiles.value) {
    // ###更新上传状态数据
    prepareUploadInfo.value[item.fileMd5].uploading = true

    const fileValue = item.fileValue
    // 重组检验数据
    const params = {
      fileName: fileValue.name,
      fileMd5: item.fileMd5,
      chunkTotal: allSlices.value[item.fileMd5].length,
    }

    console.log(params, 'get')
    // 请求后端校验
    const res = await BreakPointAPI.findFile(params)
    console.log(res, 'findfilejiekou')

    if (res.code === 0) {
      const IsFinish = res.data.file.IsFinish
      if (!IsFinish) {
        // 对没上传文件或只有部分切片上传的文件处理
        const finishList = res.data.file.ExaFileChunk
        // 生成最终待上传切片数据
        waitSlices.value[item.fileMd5] = allSlices.value[item.fileMd5].filter(
          (all: any) => {
            return !(
              finishList &&
              finishList.some(
                (fi: { FileChunkNumber: any }) =>
                  fi.FileChunkNumber === (all as { key: any }).key
              )
            )
          }
        )
      } else {
        waitSlices.value[item.fileMd5] = []
        // ###更新上传状态数据
        prepareUploadInfo.value[item.fileMd5].uploadProgress = 100
        prepareUploadInfo.value[item.fileMd5].uploaded = true
        prepareUploadInfo.value[item.fileMd5].transcoding = true
        prepareUploadInfo.value[item.fileMd5].transcodeProgress = 100
        prepareUploadInfo.value[item.fileMd5].transcoded = true
        ElMessage.success('已上传过，秒传')
        emit('breakpointContinueFinishE', {
          ...res.data,
          folderIDd: folderIDd.value,
          emitType: 3,
          emitMsg: '已上传过，秒传',
        })
      }
    }
  }

  prepareFiles.value = []
}

/**
 * 获取文件/切片 的md5数据
 * @param file 文件或者前片
 */
const getFileMd5 = (file: Blob) => {
  return new Promise<string>((resolve) => {
    const fileR = new FileReader()
    fileR.readAsArrayBuffer(file)

    fileR.onload = (e) => {
      const blob = e.target!.result
      const spark = new SparkMD5.ArrayBuffer()
      spark.append(blob as ArrayBuffer)
      const fileMd5: string = spark.end()
      resolve(fileMd5)
    }
  })
}

/**
 * 获取上传文件的扩展名
 * @param filename 文件名
 */
const getFileExtension = (filename: string) => {
  return filename.split('.').pop()
}

/**
 * 获取文件类型（1:图片、2:视频、3:音频、4:未知）
 * @param filename 文件名
 */
function getFileTypeByExtension(filename: string) {
  // 常用图片格式后缀
  const imageExtensions = [
    'png',
    'bmp',
    'jpg',
    'jpe',
    'svg',
    'jpeg',
    'tiff',
    'tif',
    'ico',
    'webp',
    'jxr',
    'hdp',
    'raw',
    'psd',
    'ai',
  ]

  // 常用视频格式后缀
  const videoExtensions = [
    'mp4',
    '3gp',
    'avi',
    'wmv',
    'webm',
    'mkv',
    'vob',
    'ojv',
    'rmvb',
    'mpeg',
    'mpg',
    'mov',
    'flv',
    'swf',
    'qsv',
    'kux',
  ]

  // 常用音频格式后缀
  const audioExtensions = [
    'mp3',
    'ac3',
    'amr',
    'ogg',
    'mp2',
    'm4a',
    'aac',
    'flac',
    'mp1',
    'wav',
    'wma',
    'aif',
    'aiff',
    'au',
    'ram',
    'mid',
    'rmi',
  ]

  // const audioExtensions = ['mp3', 'wav', 'ogg', 'aac', 'flac', 'wma', 'm4a']
  // const videoExtensions = ['mp4', 'avi', 'mkv', 'mov', 'wmv', 'flv', 'webm']
  // const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg']

  const extension = filename.toLowerCase().substr(filename.lastIndexOf('.'))
  console.log(extension, 'extensionextensionextension')

  if (audioExtensions.map((ext) => '.' + ext).includes(extension)) {
    return '3'
  } else if (videoExtensions.map((ext) => '.' + ext).includes(extension)) {
    return '2'
  } else if (imageExtensions.map((ext) => '.' + ext).includes(extension)) {
    return '1'
  } else {
    return '4'
  }
}

/**
 * 重新格式化文件名（将特殊字符和空格替换为 '-'）
 * @param str 文件名
 */
const formatStrA = (str: string) => {
  let lastPart = ''
  let index = str.lastIndexOf('.') // 找到最后一个小数点的位置

  if (index !== -1) {
    lastPart = str.slice(index) // 保存最后一个小数点及其后的字符
    str = str.slice(0, index) // 去掉最后一个小数点及其后的字符
  }

  str = str.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, '-') // 将特殊字符和空格替换为 '-'
  str = str.replace(/-+/g, '-') // 将多个连续的 '-' 替换为一个 '-'
  str = str.slice(0, 100) // 保留前100字符
  str = str + lastPart // 将最后一个小数点及其后的字符添加回去
  return str
}

/**
 * 创建切片数组：将文件分割为切片数组，每个单元有（fileMd5标识，切片后文件，切片计数，切片文件名）
 * @param file
 * @param fileMd5
 */
const createFileSlice = (file: File, fileMd5: string | Blob) => {
  console.log(file)

  const FileSliceCap = 1 * 1024 * 1024 // 分片字节数
  let start = 0 // 定义分片开始切的地方
  let end = 0 // 每片结束切的地方a
  let i = 0 // 第几片
  let formDataList = [] // 分片存储的一个池子 丢全局
  while (end < file.size) {
    // 当结尾数字大于文件总size的时候 结束切片
    start = i * FileSliceCap // 计算每片开始位置
    end = (i + 1) * FileSliceCap // 计算每片结束位置
    let fileSlice = file.slice(start, end) // 开始切  file.slice 为 h5方法 对文件切片 参数为 起止字节数
    console.log(fileSlice, 'fileSlice')

    const formData = new window.FormData() // 创建FormData用于存储传给后端的信息
    // console.log(fileMd5.value, 'fileMd5.value')
    formData.append('fileMd5', fileMd5) // 存储总文件的Md5 让后端知道自己是谁的切片
    formData.append('file', fileSlice) // 当前的切片
    formData.append('chunkNumber', i.toString()) // 当前是第几片
    formData.append('fileName', formatStrA(file.name)) // 当前文件的文件名 用于后端文件切片的命名  formData.appen 为 formData对象添加参数的方法
    // console.log(formData, 'formdatasss')
    formDataList.push({ key: i, formData, isFinish: false }) // 把当前切片信息 自己是第几片 存入我们方才准备好的池子
    i++
  }

  allSlices.value[fileMd5 as string] = formDataList
}

/**
 * 判断文件是否允许上传：类型是否支持上传，判断文件大小是否合规
 * @param file
 */
const checkFile = (file: File) => {
  let isAllowUpload = true
  // 判断类型
  if (!allowFileTypes.value.includes(file.type.toLowerCase())) {
    isAllowUpload = false
    console.log('暂不支持该资源格式', file.name)

    // return ElMessage('暂不支持该资源格式')
  }
  // 判断大小
  if (!(file.size < maxSize.value && file.size > 0)) {
    console.log('文件大小不合规', file.name)
    isAllowUpload = false
  }
  return isAllowUpload
}

const startUpload = () => {
  // files.value.forEach((file) => {
  //   if (!file.uploaded && !file.uploading) {
  //     uploadFile(file)
  //   }
  // })
}

const formatSize = (size: number) => {
  const units = ['B', 'KB', 'MB', 'GB']
  let index = 0
  while (size >= 1024 && index < units.length - 1) {
    size /= 1024
    index++
  }
  return `${size.toFixed(2)} ${units[index]}`
}

defineExpose({
  openFileInput,
})
</script>

<style scoped lang="scss">
.uploadnew {
  display: inline-flex;
}
.uploadbtn {
  display: inline-flex;
}
</style>
